﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Skugo.Core;
using Skugo.Shared;

namespace Skugo.StandardPlugins
{
    /// <summary>
    /// Allows the user to immediately draw objects (such as lines) for debugging purposes.
    /// </summary>
    public class DebugDrawer
    {
        /// <summary>
        /// The maximum number of vertices that the debug drawer can draw.
        /// </summary>
        private const Int32 MaxVertexCount = 65536;

        /// <summary>
        /// A list of all the debug lines we need to draw.
        /// </summary>
        private List<DebugLine> DebugLines = new List<DebugLine>();
        private List<DebugLine> DebugLinesQueue = new List<DebugLine>();

        /// <summary>
        /// The dynamic vertex buffer that stores the quad's vertices for each sprite to be drawn.
        /// </summary>
        private DynamicVertexBuffer DebugVertexBuffer;

        /// <summary>
        /// A list of all the vertices that we'll constantly be uploading to the dynamic vertex buffer.
        /// </summary>
        private DebugVertex[] Vertices = new DebugVertex[MaxVertexCount];

        /// <summary>
        /// The effect we'll use for debug drawing.
        /// </summary>
        private Effect DebugDrawEffect;

        /// <summary>
        /// The transform is the world to projection transform (we assume all debug draw is in world space).
        /// </summary>
        private EffectParameter TransformParameter;

        /// <summary>
        /// The graphics device allows us to draw to the back buffer.
        /// </summary>
        private GraphicsDevice Device;

        /// <summary>
        /// The vertex declaration used for debug draw.
        /// </summary>
        private VertexDeclaration VertexDeclaration;

        /// <summary>
        /// Initializes a new instance of the DebugDrawer class.
        /// </summary>
        /// <param name="device">The device which we'll be using to draw with.</param>
        public DebugDrawer(GraphicsDevice device, Effect debugDrawEffect)
        {
            // Store the device away
            this.Device = device;

            // Create the vertex declaration
            this.VertexDeclaration = new VertexDeclaration(new VertexElement[]
            {
                new VertexElement(0,  VertexElementFormat.Vector3,  VertexElementUsage.Position,    0),
                new VertexElement(12, VertexElementFormat.Color,    VertexElementUsage.Color,       0),
            });

            // Create the dynamic vertex buffer that we'll use to render out debug draw
            this.DebugVertexBuffer = new DynamicVertexBuffer(device, this.VertexDeclaration, MaxVertexCount, BufferUsage.WriteOnly);

            // Store the debug draw effect
            this.DebugDrawEffect = debugDrawEffect;

            // Store away the transform parameter
            this.TransformParameter = this.DebugDrawEffect.Parameters["Transform"];
        }

        /// <summary>
        /// Debug draw a line in world space.
        /// </summary>
        /// <param name="p1">The first point on the line segment.</param>
        /// <param name="p2">The second point on the line segment.</param>
        /// <param name="color">The color of the line.</param>
        /// <param name="duration">How long the line should be visible, in seconds. (Use 0 for 'just this frame')</param>
        public void DrawLineSegment(Vector3 p1, Vector3 p2, Color color)
        {
            // Create a debug line structure
            DebugLine debugLine = new DebugLine();

            // Setup the debug line structure
            debugLine.P1 = p1;
            debugLine.P2 = p2;
            debugLine.Color = color;

            // Store the data away for the frame update
            this.DebugLinesQueue.Add(debugLine);
        }

        ///// <summary>
        ///// Debug draw a box in world space.
        ///// </summary>
        ///// <param name="min">The minimal point of the box.</param>
        ///// <param name="max">The maximal point of the box.</param>
        ///// <param name="zValue">The z-value that the box should be drawn at.</param>
        ///// <param name="justCorners">Only draw the corners of the box.</param>
        ///// <param name="color">The color of the box.</param>
        ///// <param name="duration">How long the line should be visible, in seconds. (Use 0 for 'just this frame')</param>
        //public void DrawBox(Vector2 min, Vector2 max, Single zValue, Boolean justCorners, Color color, Single duration)
        //{
        //    // p1--p2
        //    // |    |
        //    // |    |
        //    // p4--p4

        //    // Create the points of the box
        //    Vector3 p1 = new Vector3(min.X, max.Y, zValue);
        //    Vector3 p2 = new Vector3(max.X, max.Y, zValue);
        //    Vector3 p3 = new Vector3(max.X, min.Y, zValue);
        //    Vector3 p4 = new Vector3(min.X, min.Y, zValue);

        //    // If we just want to draw the corners...
        //    if (justCorners)
        //    {
        //        // Draw segments between each of the points on the perimeter
        //        this.DrawLineSegment(p1, Vector3.Lerp(p1, p2, 0.25f), color, duration);
        //        this.DrawLineSegment(p2, Vector3.Lerp(p2, p1, 0.25f), color, duration);
        //        this.DrawLineSegment(p2, Vector3.Lerp(p2, p3, 0.25f), color, duration);
        //        this.DrawLineSegment(p3, Vector3.Lerp(p3, p2, 0.25f), color, duration);
        //        this.DrawLineSegment(p3, Vector3.Lerp(p3, p4, 0.25f), color, duration);
        //        this.DrawLineSegment(p4, Vector3.Lerp(p4, p3, 0.25f), color, duration);
        //        this.DrawLineSegment(p4, Vector3.Lerp(p4, p1, 0.25f), color, duration);
        //        this.DrawLineSegment(p1, Vector3.Lerp(p1, p4, 0.25f), color, duration);
        //    }
        //    else
        //    {
        //        // Draw segments between each of the points on the perimeter
        //        this.DrawLineSegment(p1, p2, color, duration);
        //        this.DrawLineSegment(p2, p3, color, duration);
        //        this.DrawLineSegment(p3, p4, color, duration);
        //        this.DrawLineSegment(p4, p1, color, duration);
        //    }
        //}

        public void SwapDebugList()
        {
            var swapped = this.DebugLines;
            swapped.Clear();
            this.DebugLines = this.DebugLinesQueue;
            this.DebugLinesQueue = swapped;
        }

        /// <summary>
        /// Draw all the queued up objects.
        /// </summary>
        /// <param name="frameTime">The amount of time that's passed between frames.</param>
        /// <param name="worldToProjectionTransform">The world to projection transform that we use for transforming the debug draw onto the screen.</param>
        public void DrawAll(Single frameTime, Matrix worldToProjectionTransform)
        {
            // Store the vertex count
            Int32 vertexCount = 0;

            // Loop through all the debug lines
            for (Int32 i = 0; i < this.DebugLines.Count; ++i)
            {
                // Get a reference to the current line
                DebugLine line = this.DebugLines[i];

                // Write out the vertex
                this.Vertices[vertexCount + 0].Position = line.P1;
                this.Vertices[vertexCount + 0].Color = line.Color;
                this.Vertices[vertexCount + 1].Position = line.P2;
                this.Vertices[vertexCount + 1].Color = line.Color;

                // We just added two vertices...
                vertexCount += 2;
            }

            // As long as we actually have vertices to draw...
            if (vertexCount > 0)
            {
                // Set the transform parameter
                this.TransformParameter.SetValue(worldToProjectionTransform);

                // Apply the debug drawing technique
                this.DebugDrawEffect.CurrentTechnique.Passes[0].Apply();

                // Write the data to the dynamic vertex buffer
                this.DebugVertexBuffer.SetData<DebugVertex>(this.Vertices, 0, vertexCount);

                // Set the vertex buffer
                this.Device.SetVertexBuffer(this.DebugVertexBuffer);

                // Draw all the lines
                this.Device.DrawPrimitives(PrimitiveType.LineList, 0, vertexCount / 2);
            }
        }

        /// <summary>
        /// The debug vertex type that gets used for debug drawing.
        /// </summary>
        private struct DebugVertex
        {
            /// <summary>
            /// The position of the vertex.
            /// </summary>
            public Vector3 Position;

            /// <summary>
            /// The color of the vertex.
            /// </summary>
            public Color Color;
        }

        /// <summary>
        /// The line structure describes a debug line that's currently drawing
        /// </summary>
        private class DebugLine
        {
            /// <summary>
            /// The first point on the line
            /// </summary>
            public Vector3 P1;

            /// <summary>
            /// The second point on the line
            /// </summary>
            public Vector3 P2;

            /// <summary>
            /// The color of the line.
            /// </summary>
            public Color Color;
        }
    }

    struct DebugDrawInfo
    {
        public Color Color;
        public Single Duration;
        public Int32 Priority;
        public TransformSpace Space;
    }
}
